<?php

/**
 * Diese DAO dient der Kapselung aller Datenbank Oprationen,
 * welche auf dem User basieren.
 * 
 * @author Shane
 * TODO delete($id); delete($username);
 */
class UserDAO {
	// debug-logger
	static $debug;
	
	// Das DB Objekt
	private $db;
	
	// Hier steht immer der aktuellste Fehler drin
	private $error = null;
	
	/**
	 * Der Konstruktor stattet die DAO mit einem DB Objekt aus
	 */
	function __construct() {
		if (!isset(self::$debug)) {
			self::$debug = DebugQuery::getInstance();
		}
		$this->db = DBFactory::getInstance();
	}
	
	/**
	 * Der Destruktor entfernt die Referen auf das DB Objekt
	 */
	function __destruct() {
		unset($this->db);
	}
	
	/**
	 * Diese Methode gibt ein array mit allen Usern zurück.
	 * 
	 * Jeder User wird hierbei durch ein User Objekt representiert.
	 * 
	 * @return numerisches array mit User Objekten, wenn erfolgreich, sonst null und Fehler in error
	 */
	public function get_all() {
		// Wenn vorher ein Fehler aufgetreten ist, schlucken
		$this->handle_previous_error();
		
		$ret = array();
		
		// debug
		if (DEBUG_ENABLED)
			self::$debug->append("Loading all entities of User from Database...\n");
			
		// Query zum laden aller User aus der DB
		$sql = 'SELECT
					ID,
					Username,
					Email
				FROM
					User';
		
		// Query vorbereiten
		if (!$stmt = $this->db->prepare($sql)) {
			// Error in der DB Instanz
			$this->error = ERR_DATABASE_RETURNS_ERROR. $this->db->error;
			
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("Error in database object: %s\n", $this->error);
			
			return null;
		}
		
		// Query ausführen
		if (!$stmt->execute()) {
			// Error im Statement
			$this->error = ERR_STATEMENT_RETURNS_ERROR. $stmt->error;
			
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("Error in statment: %s\n", $this->error);
			
			return null;
		}
		
		// Ergebnis auslesen
		$stmt->bind_result($id, $username, $email);
		while ($stmt->fetch()) {
			$ret[] = new User($id, $username, $email);
		}
		
		// Statement schließen
		$stmt->close();
		
		// debug
		if (DEBUG_ENABLED)
			self::$debug->append("Successfully loaded %d Users from database\n", count($ret));
		
		return $ret;
	}
	
	/**
	 * Diese Methode liest den Benutzer mit der id aus der Datenbank
	 * 
	 * @param integer $id id des Benutzers
	 * @return User wenn vorhanden, sonst null
	 */
	public function getById($id) {
		$this->handle_previous_error();
		
		$ret = null;
		
		// debug
		if (DEBUG_ENABLED)
			self::$debug->append("Loading User with id '%d' from database...\n", $id);
		
		// Query zum laden des Benutzers mit der entsprechenden ID
		$sql = 'SELECT
					ID,
					Username,
					Email
				FROM
					User
				WHERE
					ID = ?';
		
		// Query vorbereiten
		if (!$stmt = $this->db->prepare($sql)) {
			// Error in der DB Instanz
			$this->error = ERR_DATABASE_RETURNS_ERROR. $this->db->error;
			
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("Error in database object: %s\n", $this->error);
			
			return null;
		}
		
		// Platzhalter ersetzen
		$stmt->bind_param('i', $id);
		
		// Query ausführen
		if (!$stmt->execute()) {
			// Error im Statement
			$this->error = ERR_STATEMENT_RETURNS_ERROR. $stmt->error;
			
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("Error in statment: %s\n", $this->error);
			
			return null;
		}
		
		// Ergebnis an variablen binden
		$stmt->bind_result($userid, $username, $email);
		
		// Es dürfte höchstens ein User gefunden worden sein
		while($stmt->fetch()) {
			$ret = new User($userid, $username, $email);
		}
		
		// Statement schließen
		$stmt->close();
		
		// debug
		if (DEBUG_ENABLED)
			self::$debug->append($ret ? "Loading User with id '%d' was successfull.\n" : "No User found with id '%d.\n", $id);
		
		return $ret;
	}
	
	/**
	 * Diese Methode liest den Benutzer mit dem Benutzernamen aus der DB
	 * 
	 * @param string $username
	 * @return User wenn vorhanden, sonst null
	 */
	public function getByName($username) {
		$this->handle_previous_error();
		
		$ret = null;
		
		// debug
		if (DEBUG_ENABLED)
			self::$debug->append("Loading User with Username '%s' from database...\n", $username);
			
		// Query zum laden eines Users mit vorgegebenem Username
		$sql = 'SELECT
					ID,
					Username,
					Email
				FROM
					User
				WHERE
					Username = ?';
		
		// Query vorbereiten
		if (!$stmt = $this->db->prepare($sql)) {
			// Error in der DB Instanz
			$this->error = ERR_DATABASE_RETURNS_ERROR. $this->db->error;
			
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("Error in database object: %s\n", $this->error);
			
			return null;
		}
		
		// Platzhalter ersetzen
		$stmt->bind_param('s', $username);
		
		// Query ausführen
		if (!$stmt->execute()) {
			// Error im Statement
			$this->error = ERR_STATEMENT_RETURNS_ERROR. $stmt->error;
			
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("Error in statment: %s\n", $this->error);
			
			return null;
		}
		
		// Ergebnis an variablen binden
		$stmt->bind_result($userid, $loaded_username, $email);
		
		// Es dürfte höchstens ein User gefunden worden sein
		while($stmt->fetch()) {
			$ret = new User($userid, $loaded_username, $email);
		}
		
		// Statement schließen
		$stmt->close();
		
		// debug
		if (DEBUG_ENABLED)
			self::$debug->append($ret ? "Loading User with username '%s' was successfull.\n" : "No User found with username '%s.\n", $username);
		
		return $ret;
	}
	
	/**
	 * Diese Methode speichert einen Benutzer in die Datenbank
	 * 
	 * @param User $user der Benutzer
	 * @return UserID, falls erfolgreich, sonst null und Fehler in error
	 */
	public function save($user) {
		$this->handle_previous_error();
		
		$ret = null;
		
		// debug
		if (DEBUG_ENABLED)
			self::$debug->append("Saving the User '%s' to the database...", $user->get_username());
		
		// Prüfen, ob es den User schon gibt
		if ($this->getByName($user->get_username())) {
			$this->error = ERR_USERNAME_ALREADY_IN_USE;
			
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("Username '%s' is already in use.", $user->get_username());
			
			return null;
		} else if ($err = $this->get_error()) {
			$this->error = ERR_LOADING_USER_PRODUCED. $err;
			
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("Error checking Username: %s", $err);
			
			return null;
		}
			
		// Query zum Speichern eines Users
		$sql = 'INSERT INTO
					User(Username, Email, Password)
				VALUES
					(?, ?, ?)';
		
		// Statement vorbereiten
		if (!$stmt = $this->db->prepare($sql)) {
			// Error in der DB Instanz
			$this->error = ERR_DATABASE_RETURNS_ERROR. $this->db->error;
			
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("Error in database object: %s\n", $this->error);
			
			return null;
		}
		
		// Platzhalter ersetzen
		$stmt->bind_param('sss', $user->get_username(), $user->get_email(), $user->get_password());
		
		// Query ausführen
		if (!$stmt->execute()) {
			// Error im Statement
			$this->error = ERR_STATEMENT_RETURNS_ERROR. $stmt->error;
			
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("Error in statment: %s\n", $this->error);
			
			return null;
		}
		
		// Userid auslesen
		$ret = $stmt->insert_id;
		
		// Gab es einen Fehler?
		if (!$ret) {
			// Error im Statement
			$this->error = ERR_STATEMENT_RETURNS_ERROR. $stmt->error;
			
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("Error in statment: %s\n", $this->error);
			
			return null;
		}
		
		// Statement schließen
		$stmt->close();
		
		// debug
		if (DEBUG_ENABLED)
			self::$debug->append("User '%s' was saved successfully.\n", $user->get_username());
		
		return $ret;
	}
	
	/**
	 * Diese Methode updated den User, allerdings nur, wenn dieser sich mit dem pw
	 * verifiieren lässt.
	 * 
	 * @param User $user - der User
	 * @param string $password - unhashed old pw
	 * @return UserID wenn erfolgreich, sonst null und Fehler in error
	 */
	public function update($user, $password) {
		$this->handle_previous_error();
		
		$ret = null;
		
		// debug
		if (DEBUG_ENABLED)
			self::$debug->append("Updating User '%s'...\n", $user->get_username());
			
		// User aus der DB laden, wegen dem alten Username
		if (!$old_user = $this->getById($user->get_id())) {
			// Wenn es keinen Fehler gab
			if (!$err = $this->get_error()) {
				// dann gibt es den Benutzer nicht
				$this->error = ERR_USER_DOES_NOT_EXSIST. $user.get_id();
				
				// debug
				if (DEBUG_ENABLED)
					self::$debug->append("User with id '%d' was not found.\n", $user->get_id());
					
				return null;
			}
			// Wenn es einen Fehler gab
			else {
				// dann error erweitern
				$this->error = ERR_LOADING_USER_PRODUCED. $err;
				
				// debug
				if (DEBUG_ENABLED)
					self::$debug->append("Error loading original User: %s\n", $err);
					
				return null;
			}
		}
		
		// Passwort hashen
		$hash = md5(md5($old_user->get_username()).$password);
			
		// User verifizieren
		if (!$this->verify($old_user->get_username(), $hash)) {
			// In der verify Methode ist kein Fehler aufgetreten?
			if (!$err = $this->get_error()) {
				$this->error = ERR_INVALID_PASSWORD;
				
				// debug
				if (DEBUG_ENABLED)
					self::$debug->append("User '%s' could not be verifyed.\n", $old_user->get_username());
					
				return null;
			} 
			// In der verify ist ein Fehler aufgetreten
			else {
				$this->error = ERR_VERIFYING_USER. $err;
				
				// debug
				if (DEBUG_ENABLED)
					self::$debug->append("Error verifying User: %s\n", $err);
				
				return null;
			}
		}
		
		// Query zum aktualisieren des Benutzers
		$sql = 'UPDATE
					User
				SET
					Username = ?,
					Email = ?,
					Password = ?
				WHERE
					ID = ?';
		
		// Statement vorbereiten
		if (!$stmt = $this->db->prepare($sql)) {
			// Error in der DB Instanz
			$this->error = ERR_DATABASE_RETURNS_ERROR. $this->db->error;
			
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("Error in database object: %s\n", $this->error);
			
			return null;
		}
		
		// Platzhalter ersetzen
		$stmt->bind_param('sssi', $user->get_username(), $user->get_email(), $user->get_password(), $user->get_id());
		
		// Query ausführen
		if (!$stmt->execute()) {
			// Error im Statement
			$this->error = ERR_STATEMENT_RETURNS_ERROR. $stmt->error;
			
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("Error in statment: %s\n", $this->error);
			
			return null;
		}
		
		// Statement schließen
		$stmt->close();
		
		// debug
		if (DEBUG_ENABLED)
			self::$debug->append("User '%s' successfully updated.", $user->get_username());
		
		// An dieser Stelle ist alles super, deswegen id zurückgeben
		$ret = $user->get_id();
		
		return $ret;
	}
	
	/**
	 * Diese Methode verifiziert einen Username mit einem pw
	 * 
	 * @param string $username
	 * @param string $password unhashed pw
	 * @return UserID, wenn erfolgreich, null falls nicht und Fehler in error
	 */
	public function verify($username, $password) {
		$this->handle_previous_error();
		
		$ret = null;
		
		// debug
		if (DEBUG_ENABLED)
			self::$debug->append("Verifying User '%s'...\n", $username);
			
		// Gibt es den User ?
		if (!$user = $this->getByName($username)) {
			// Wenn in getByName kein Fehler aufgetreten ist
			if (!$err = $this->get_error()) {
				// dann gibt es den User nicht
				$this->error = ERR_USERNAME_DOES_NOT_EXSIST;
				
				// debug
				if (DEBUG_ENABLED)
					self::$debug->append("Username '%s' does not exsist.\n", $username);
				
				return null;
			}
			// Wenn ein Fehler aufgetreten ist
			else {
				// dann Fehler erweitern
				$this->error = ERR_LOADING_USER_PRODUCED. $err;
				
				// debug
				if (DEBUG_ENABLED)
					self::$debug->append("Error loading User '%s': '%s'\n", $username, $err);
					
				return null;
			}
		}
			
		// passwort hashen
		$hash = md5(md5($username).$password);
		
		// stimmen die Passwörter über ein ?
		if (!($user->get_password() != $hash)) {
			// stimmen nicht überein
			$this->error = ERR_INVALID_PASSWORD;
			
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("Password is invalid.");
				
			return null;
		}
		
		// debug
		if (DEBUG_ENABLED)
			self::$debug->append("Successfully verifyed User '%s'\n", $username);
			
		// Da passwort stimmt soll die id zurück gegeben werden
		$ret = $user->get_id();
		
		return $ret;
	}
	
	/**
	 * Diese Methode löscht die User Entität mit der übergebenen ID
	 * 
	 * @param integer $id
	 */
	public function deleteById($id) {
		// Wenn vorher ein Fehler aufgetreten ist, schlucken
		$this->handle_previous_error();
		
		$ret = null;
		
		// debug
		if (DEBUG_ENABLED)
			self::$debug->append("Deleting User with id '%d'\n", $id);
			
		// Query zum löschen der Right Entität
		$sql = 'DELETE FROM
					User
				WHERE
					ID = ?';
		
		// Statement vorbereiten
		if (!$stmt = $this->db->prepare($sql)) {
			// Error in der DB Instanz
			$this->error = ERR_DATABASE_RETURNS_ERROR. $this->db->error;
			
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("Error in database object: %s\n", $this->error);
			
			return null;
		}
		
		// Platzhalter ersetzen
		$stmt->bind_param('i', $id);
		
		// Query ausführen
		if (!$stmt->execute()) {
			// Error im Statement
			$this->error = ERR_STATEMENT_RETURNS_ERROR. $stmt->error;
			
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("Error in statment: %s\n", $this->error);
			
			return null;
		}
		
		// Statement schließen
		$stmt->close();
		
		// Probieren, ob es die Entität noch gibt
		if ($this->getById($id)) {
			// Löschen hat nicht geklappt
			$this->error = ERR_ENTITY_WAS_NOT_DELETED. $id;
			
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("User entity with id '%d' was not deleted.", $id);
				
			return null;
		}
		
		// debug
		if (DEBUG_ENABLED)
			self::$debug->append("User entity successfully deleted.");
		
		return $ret;
	}
	
	/**
	 * Gibt den error aus und leert den internen error cache
	 */
	public function get_error() {
		$err = $this->error;
		$this->error = null;
		return $err;
	}
	
	/**
	 * Behandelt vorhergegangene nicht ausgelesene errors
	 */
	private function handle_previous_error() {
		// Falls vorher mal ein error aufgetreten ist, jedoch der
		// Fehler nicht ausgelesen wurde, wird dieser jetzt verworfen
		if ($pre_error = $this->get_error()) {
			// debug
			if (DEBUG_ENABLED)
				self::$debug->append("Previous error was not read: '%s'\n", $pre_error);
		}
	}
}

?>